Blue-Green Deploymentにおける注意点
こんにちは。こむろです。 今年の札幌の夏はハードモードだ(湿気と暑さ)
この先生きのこるためにエアコンが投入されました。
はじめに
クラウドネイティブなアプリケーションを設計・構築・運用している皆さんは、普段どのようにアプリケーションやインフラの更新作業を行っているでしょうか。
順次インスタンスやコンテナを切り替えていくRolling Update?それとも環境を複製してDNS Routingの切り替えによるBlue-Green Deploymentでしょうか。他にも様々な方法があるかと思いますが、今回もまたBlue-Green Deploymentにおける実際の現場で発生した事象について報告したいと思います。
あまりネット上にもこういった情報が出てこないようなのですが、皆さんこういった問題は軽々とクリアされているのでしょうか。自分がポンコツなだけなのかととても不安にかられるばかりです。
Rolling Update
ECS等で運用しているシステムの場合は、コンテナを順次入れ替えてアプリケーションを更新することで徐々にアプリケーションの更新を適用していく更新の手法です。LoadBalancer配下に複数の独立した同じアプリケーションを平行に展開することで、順次入れ替えていくことができます。このアプリケーション更新の場合は以下のようなメリット・デメリットがあります。
メリット
- インフラストラクチャの変更がないため、利用料金が殆ど変化しない
- 安定して稼働しているインフラストラクチャへの変更がないため安定
- 後述するClientの問題が発生しない
デメリット
- インフラストラクチャの変更がある場合採用しづらい
- アプリケーションの起動速度とLoad Balancerへの参加のタイミングによっては、一時的に既存のインスタンスやコンテナに負荷が集中する可能性がある
今回は主題ではないため詳細は省略しますが、k8sやECS等のコンテナで稼働する環境でアプリケーション更新する際には、こちらのDeployment方法を採用することが多いのではないでしょうか。インフラストラクチャの更新もないため、比較的作業としても小さく、必要最小限のみの変更で処理能力さえうまく担保できていれば非常に早く更新ができる手法かと思います。
Blue-Green Deployment
基本的にAWSに限らず、クラウドで構築されたアプリケーションのインフラストラクチャは、Immutableで冪等性のある形で管理されているかと思います。そのため、アプリケーションが稼働するインフラストラクチャ及び実行環境を複製することができます。これによりBlue-Green Deploymentというアプリケーション更新の手法が実用されているのはご存知のとおりです。
Publickey - 「Blue-Green Deployment」とは何か、マーチン・ファウラー氏の解説
各クラウドサービスにおいてもBlue-Green Deploymentに関する記述を確認することができます。
- AWSのBlue/Green Deploy
- Blue-Green Deployments using Azure Traffic Manager
- GCP ソリューション GCPへの.NETアプリのデプロイ
Rolling Updateに比べてインフラストラクチャを一時的に複製してRequestが流入しない環境でテストすることができるため、インフラストラクチャの変更やk8sのオーケストレーションツールのメジャーバージョンアップなど、ミドルウェアのアップデートが必要な場合など、こちらの更新手法を採用するのが良いのかと思います。
Blue-Green Deploymentのおさらい
ここで再度Blue-Green Deploymentについておさらいします。
Blue-Green Deploymentでは、現在稼働しているインフラストラクチャをはじめミドルウェア、アプリケーションをそのままコピーし新たに作成します。
そのため、一時的に稼働するインフラのリソースの数は2倍に増えます。
ただし、データストアなどは複製した環境でも共通で参照する可能性もあるため、インフラのリソースが全て複製されるわけではありません。
その後、Requestの流入先のターゲットを切り替えます。
この際に、特定のRequestのみを新しい環境へ振り分けるのか、全体のXX%を振り分けるのか、全量を一気に振り分けるのかの違いはありますが、何らかの方法でRequestの流入先を新たな環境へ切り替えます。
AWSの場合はRoute 53(DNSサービス)を利用することが多いのではないでしょうか。
Deploymentの実行手順
まず前提としてBlue環境で稼働しているアプリケーションがあるとします。ここでのアプリケーションVersionは 1.0
とします。
この環境をベースに新たな環境を複製します。
複製する前提としてImmutableな環境を作成できるよう、コードや設定ファイルで管理している必要があります。 CloudFormationやAnsibleを始めとする各種ツール、マネージドサービスを利用するとElasticBeanstalkやCodeDeploy等でしょうか。
今回の例ではデータストアは共通で持つようにしています。
この場合、何点か注意が必要です。新たにDeployされるアプリケーションでは、データストアに格納するデータの 構造に変化を与えない ことが求められます。旧環境と新環境において共通に利用するデータに不可逆な変更を与えた場合、以下のような状況が発生する可能性があるためです。
- 新アプリケーションで不具合が見つかった際に、旧環境への切り戻しが不可能になる可能性
- 一時的に旧環境と新環境が混在する可能性になった場合、Requestの振り分け次第では新環境で処理した後、次のRequestで旧環境で処理される可能性
もしデータ構造に不可逆な変化を与える場合、旧環境のアプリケーションでもそれが正常に動作することを確認しなければなりません。そうでなければ Rollback不可能な一方通行のDeployとする 覚悟が必要になります(Blue-Green Deploymentでも切り戻せない)
データストアもすべて複製する場合は、新たに複製したデータストアに対して不可逆な変更を行っても特に問題ありません。ただし、切り戻す場合には新たなデータストアへ流入してしまったRequestの扱いをどうするか(切り捨てるのかデータ差分を取り込むのか)を確認する必要があります。
続きです。新たな環境のアプリケーションは Version 2.0
とします。
Route53で、指定されたドメインに対してのRequestの流入先を新たに構築したGreen環境のLoad Balancerへ振り分けます。これにより、Client側はサーバーアプリケーションの環境の変更に影響されることなく、同じドメイン名でサーバーアプリケーションを利用することができます。
あとは、RequestがなくなったBlue環境のインフラストラクチャを縮退すればダウンタイムなしにサーバーアプリケーションの更新が可能なはずです。
実際の問題点
では、実際にこのようにDeploymentが理想通りにうまくいくかと言うと、必ずしもそうではありません。
何が問題か。それはClientのCacheの問題です。本来ClientはDNSへ問い合わせを行いLoad BalancerのIPを取得し、そのLoad BalancerへRequestしているはずです。しかし、実際はどうかというと以下のようになっています。
Client側の通信ライブラリや実行基盤がDNSの問い合わせ結果をCacheしています。これは動作の最適化であったり、セキュリティ脆弱性への対応によるものです。
特にJVMのDNS Cacheの件については、以前から様々な人が指摘しています。
- AWS SDK Java - DNS 名参照用の JVM TTL の設定
- Developers.IO - [JVM][EB] Elastic BeanstalkのBlue-Green Deploymentの際に注意すべきDNSキャッシュ
- 平常運転 - [JVM の DNS キャッシュを制御する](https://astj.hatenablog.com/entry/2018/03/06/130930)
- CLOVER - JavaのDNSキャッシュの有効期限を設定・確認する
加えて、OkHttp3の ConnectionPool
によって接続情報がCacheされてしまう現象を確認しました。OkHttpClientの場合、ConnectionPoolはインスタンスごとに管理しているとのこと。そのため、シングルトンで利用することが多いようです。
ConnectionPoolは接続のCacheです。そのため、一度成功した接続をCacheとして保持し、再度同じ接続先へRequestする際にPoolからCacheを取り出し利用します。
Cacheなので接続が成功している間は破棄する理由がないので維持されます。
では実際に切り替えてみるとどうなるか。
DNSのRouting先は切り替わるのですが、Client側の接続Cache情報は旧環境であるBlue環境を参照し続けます。そしてBlue環境は、DNSでRoutingはされなくてもLoad BalancerのIPは有効なのでそのまま利用し続けることができてしまいます。これにより待てど暮らせどBlue環境へのRequest流入が減らない現象が発生します。
では、この接続は最終的にどうなるか。
Blue環境を削除すると、Blue環境のLoad Balancerは削除されます。
そのためCacheしている接続先のIPは到達不可能なIPとなり、HTTP Status 503
が返却されているはずです。Client側はこのエラーに対して以下のような処理されていれば問題ありません。少々Responseに時間がかかりますが、特にエラーとはならずに正常にGreen環境へ同じRequestを行えるでしょう。
- 503を受け取った際に、ConnectionPoolを破棄してRetry
JVMのCacheの場合はRetryでは足りず、最悪アプリケーション再起動が必要かもしれません。
Medium - Survive backend redeployment with a DNS failover OkHttp client
上記リンクでは、OkHttp3でConnectionPoolを利用している場合、 どのように対応すべきかをKotlinで実際のコードと共にSolutionが提示されています。
まとめ
- Blue-Green Deploymentを行ってもきれいにRequestが移行しない場合がある
- Client側は接続情報のCacheが保持していることがあり、それらをサーバー側から制御するのは厳しい
- Cacheが多段に挟まっているとどこが原因なのかがとても把握しづらくなってきている
- クラウドならではの特性を考慮して、CacheはInfinityではなく定期的にExpireするなど工夫が必要そう
- 更新したい内容によって(例えばデータ構造の改変やインフラストラクチャの大幅な変更)は不可逆であることが多く非常に厳しいので、覚悟を持ってやりましょう
とりあえず「Blue-Green Deploymentであれば何も問題起きない」と言ってる人がいたらはっ倒して良い。 *1現実は厳しい
ここおかしくね?みたいなツッコミは大歓迎なので世の中の識者の皆様よろしくお願いします。
現場からは以上です。
脚注
- 過去の自分 ↩